بررسی نمادهای جاوا اسکریپت: هدف، ایجاد و کاربرد آنها برای کلیدهای منحصر به فرد، ذخیره فرادادهها و جلوگیری از تداخل نامها. همراه با مثالهای عملی.
نمادهای جاوا اسکریپت: کلیدهای منحصر به فرد و فرادادهها
نمادهای جاوا اسکریپت که در ECMAScript 2015 (ES6) معرفی شدند، مکانیزمی برای ایجاد کلیدهای ویژگی (property keys) منحصر به فرد و غیرقابل تغییر فراهم میکنند. برخلاف رشتهها یا اعداد، نمادها تضمین شدهاند که در کل برنامه جاوا اسکریپت شما منحصر به فرد باشند. آنها راهی برای جلوگیری از تداخل نامها، پیوست کردن فرادادهها به اشیاء بدون تداخل با ویژگیهای موجود و سفارشیسازی رفتار اشیاء ارائه میدهند. این مقاله یک نمای کلی جامع از نمادهای جاوا اسکریپت را ارائه میدهد که شامل ایجاد، کاربردها و بهترین شیوههای استفاده از آنها میشود.
نمادهای جاوا اسکریپت چه هستند؟
نماد (Symbol) یک نوع داده اولیه (primitive) در جاوا اسکریپت است، مشابه اعداد، رشتهها، بولینها، null و undefined. با این حال، برخلاف سایر انواع داده اولیه، نمادها منحصر به فرد هستند. هر بار که یک نماد ایجاد میکنید، یک مقدار کاملاً جدید و منحصر به فرد دریافت میکنید. این منحصر به فرد بودن، نمادها را برای موارد زیر ایدهآل میکند:
- ایجاد کلیدهای ویژگی منحصر به فرد: استفاده از نمادها به عنوان کلید ویژگی تضمین میکند که ویژگیهای شما با ویژگیهای موجود یا ویژگیهای اضافه شده توسط کتابخانهها یا ماژولهای دیگر تداخل نخواهند داشت.
- ذخیره فرادادهها: نمادها میتوانند برای پیوست کردن فرادادهها به اشیاء به روشی استفاده شوند که از روشهای شمارش استاندارد پنهان است و یکپارچگی شیء را حفظ میکند.
- سفارشیسازی رفتار شیء: جاوا اسکریپت مجموعهای از نمادهای شناخته شده (well-known Symbols) را ارائه میدهد که به شما امکان میدهد رفتار اشیاء را در شرایط خاصی مانند زمان تکرار یا تبدیل به رشته سفارشی کنید.
ایجاد نمادها
شما با استفاده از سازنده Symbol()
یک نماد ایجاد میکنید. مهم است که توجه داشته باشید نمیتوانید از new Symbol()
استفاده کنید؛ نمادها شیء نیستند، بلکه مقادیر اولیه هستند.
ایجاد نماد پایه
سادهترین راه برای ایجاد یک نماد این است:
const mySymbol = Symbol();
console.log(typeof mySymbol); // خروجی: symbol
هر فراخوانی Symbol()
یک مقدار جدید و منحصر به فرد تولید میکند:
const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // خروجی: false
توضیحات نماد
شما میتوانید هنگام ایجاد یک نماد، یک توضیح رشتهای اختیاری ارائه دهید. این توضیح برای اشکالزدایی (debugging) و ثبت وقایع (logging) مفید است، اما بر منحصر به فرد بودن نماد تأثیری ندارد.
const mySymbol = Symbol("myDescription");
console.log(mySymbol.toString()); // خروجی: Symbol(myDescription)
این توضیح صرفاً برای اهداف اطلاعاتی است؛ دو نماد با توضیحات یکسان همچنان منحصر به فرد هستند:
const symbolA = Symbol("same description");
const symbolB = Symbol("same description");
console.log(symbolA === symbolB); // خروجی: false
استفاده از نمادها به عنوان کلید ویژگی
نمادها به ویژه به عنوان کلید ویژگی مفید هستند زیرا منحصر به فرد بودن را تضمین میکنند و از تداخل نامها هنگام افزودن ویژگی به اشیاء جلوگیری میکنند.
افزودن ویژگیهای نماد
شما میتوانید از نمادها به عنوان کلید ویژگی دقیقاً مانند رشتهها یا اعداد استفاده کنید:
const mySymbol = Symbol("myKey");
const myObject = {};
myObject[mySymbol] = "Hello, Symbol!";
console.log(myObject[mySymbol]); // خروجی: Hello, Symbol!
جلوگیری از تداخل نامها
تصور کنید در حال کار با یک کتابخانه شخص ثالث هستید که ویژگیهایی به اشیاء اضافه میکند. ممکن است بخواهید ویژگیهای خود را بدون خطر بازنویسی ویژگیهای موجود اضافه کنید. نمادها راهی امن برای انجام این کار فراهم میکنند:
// کتابخانه شخص ثالث (شبیهسازی شده)
const libraryObject = {
name: "Library Object",
version: "1.0"
};
// کد شما
const mySecretKey = Symbol("mySecret");
libraryObject[mySecretKey] = "Top Secret Information";
console.log(libraryObject.name); // خروجی: Library Object
console.log(libraryObject[mySecretKey]); // خروجی: Top Secret Information
در این مثال، mySecretKey
تضمین میکند که ویژگی شما با هیچ یک از ویژگیهای موجود در libraryObject
تداخل نداشته باشد.
شمارش ویژگیهای نماد
یکی از ویژگیهای مهم ویژگیهای نماد این است که آنها از روشهای شمارش استاندارد مانند حلقههای for...in
و Object.keys()
پنهان هستند. این به محافظت از یکپارچگی اشیاء کمک میکند و از دسترسی یا تغییر تصادفی ویژگیهای نماد جلوگیری میکند.
const mySymbol = Symbol("myKey");
const myObject = {
name: "My Object",
[mySymbol]: "Symbol Value"
};
console.log(Object.keys(myObject)); // خروجی: ["name"]
for (let key in myObject) {
console.log(key); // خروجی: name
}
برای دسترسی به ویژگیهای نماد، باید از Object.getOwnPropertySymbols()
استفاده کنید که آرایهای از تمام ویژگیهای نماد روی یک شیء را برمیگرداند:
const mySymbol = Symbol("myKey");
const myObject = {
name: "My Object",
[mySymbol]: "Symbol Value"
};
const symbolKeys = Object.getOwnPropertySymbols(myObject);
console.log(symbolKeys); // خروجی: [Symbol(myKey)]
console.log(myObject[symbolKeys[0]]); // خروجی: Symbol Value
نمادهای شناخته شده (Well-Known Symbols)
جاوا اسکریپت مجموعهای از نمادهای داخلی را ارائه میدهد که به عنوان نمادهای شناخته شده شناخته میشوند و رفتارهای یا قابلیتهای خاصی را نشان میدهند. این نمادها ویژگیهای سازنده Symbol
هستند (مثلاً Symbol.iterator
، Symbol.toStringTag
). آنها به شما امکان میدهند رفتار اشیاء را در زمینههای مختلف سفارشی کنید.
Symbol.iterator
Symbol.iterator
نمادی است که تکرارکننده (iterator) پیشفرض را برای یک شیء تعریف میکند. وقتی یک شیء دارای متدی با کلید Symbol.iterator
باشد، آن شیء تکرارپذیر (iterable) میشود، به این معنی که میتوانید از آن با حلقههای for...of
و عملگر spread (...
) استفاده کنید.
مثال: ایجاد یک شیء تکرارپذیر سفارشی
const myCollection = {
items: [1, 2, 3, 4, 5],
[Symbol.iterator]: function* () {
for (let item of this.items) {
yield item;
}
}
};
for (let item of myCollection) {
console.log(item); // خروجی: 1, 2, 3, 4, 5
}
console.log([...myCollection]); // خروجی: [1, 2, 3, 4, 5]
در این مثال، myCollection
یک شیء است که پروتکل تکرارکننده را با استفاده از Symbol.iterator
پیادهسازی میکند. تابع مولد (generator function) هر آیتم را در آرایه items
تولید (yield) میکند و myCollection
را تکرارپذیر میسازد.
Symbol.toStringTag
Symbol.toStringTag
نمادی است که به شما امکان میدهد نمایش رشتهای یک شیء را هنگام فراخوانی Object.prototype.toString()
سفارشی کنید.
مثال: سفارشیسازی نمایش ()toString
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClassInstance';
}
}
const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // خروجی: [object MyClassInstance]
بدون Symbol.toStringTag
، خروجی [object Object]
خواهد بود. این نماد راهی برای ارائه نمایش رشتهای توصیفیتر از اشیاء شما فراهم میکند.
Symbol.hasInstance
Symbol.hasInstance
نمادی است که به شما امکان میدهد رفتار عملگر instanceof
را سفارشی کنید. به طور معمول، instanceof
بررسی میکند که آیا زنجیره прототип یک شیء حاوی ویژگی prototype
یک سازنده است یا خیر. Symbol.hasInstance
به شما امکان میدهد این رفتار را نادیده بگیرید.
مثال: سفارشیسازی بررسی instanceof
class MyClass {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyClass); // خروجی: true
console.log({} instanceof MyClass); // خروجی: false
در این مثال، متد Symbol.hasInstance
بررسی میکند که آیا نمونه (instance) یک آرایه است یا خیر. این به طور موثر باعث میشود MyClass
به عنوان یک بررسیکننده برای آرایهها عمل کند، صرف نظر از زنجیره прототип واقعی.
سایر نمادهای شناخته شده
جاوا اسکریپت چندین نماد شناخته شده دیگر را نیز تعریف میکند، از جمله:
Symbol.toPrimitive
: به شما امکان میدهد رفتار یک شیء را هنگامی که به یک مقدار اولیه تبدیل میشود (مثلاً در طول عملیات حسابی) سفارشی کنید.Symbol.unscopables
: نامهای ویژگیهایی را مشخص میکند که باید از دستوراتwith
مستثنی شوند. (استفاده ازwith
به طور کلی توصیه نمیشود).Symbol.match
،Symbol.replace
،Symbol.search
،Symbol.split
: به شما امکان میدهند رفتار اشیاء را با متدهای عبارات منظم مانندString.prototype.match()
،String.prototype.replace()
و غیره سفارشی کنید.
رجیستری سراسری نمادها
گاهی اوقات، شما نیاز دارید که نمادها را در بخشهای مختلف برنامه خود یا حتی بین برنامههای مختلف به اشتراک بگذارید. رجیستری سراسری نمادها مکانیزمی برای ثبت و بازیابی نمادها بر اساس یک کلید فراهم میکند.
Symbol.for(key)
متد Symbol.for(key)
بررسی میکند که آیا نمادی با کلید داده شده در رجیستری سراسری وجود دارد یا خیر. اگر وجود داشته باشد، آن نماد را برمیگرداند. اگر وجود نداشته باشد، یک نماد جدید با آن کلید ایجاد کرده و آن را در رجیستری ثبت میکند.
const globalSymbol1 = Symbol.for("myGlobalSymbol");
const globalSymbol2 = Symbol.for("myGlobalSymbol");
console.log(globalSymbol1 === globalSymbol2); // خروجی: true
console.log(Symbol.keyFor(globalSymbol1)); // خروجی: myGlobalSymbol
Symbol.keyFor(symbol)
متد Symbol.keyFor(symbol)
کلید مرتبط با یک نماد را در رجیستری سراسری برمیگرداند. اگر نماد در رجیستری نباشد، undefined
برمیگرداند.
const mySymbol = Symbol("localSymbol");
console.log(Symbol.keyFor(mySymbol)); // خروجی: undefined
const globalSymbol = Symbol.for("myGlobalSymbol");
console.log(Symbol.keyFor(globalSymbol)); // خروجی: myGlobalSymbol
مهم: نمادهایی که با Symbol()
ایجاد میشوند، به طور خودکار در رجیستری سراسری ثبت *نمیشوند*. فقط نمادهایی که با Symbol.for()
ایجاد (یا بازیابی) میشوند، بخشی از رجیستری هستند.
مثالهای عملی و موارد استفاده
در اینجا چند مثال عملی وجود دارد که نشان میدهد چگونه میتوان از نمادها در سناریوهای دنیای واقعی استفاده کرد:
۱. ایجاد سیستمهای پلاگین
از نمادها میتوان برای ایجاد سیستمهای پلاگین استفاده کرد که در آن ماژولهای مختلف میتوانند عملکرد یک شیء اصلی را بدون تداخل با ویژگیهای یکدیگر گسترش دهند.
// شیء اصلی
const coreObject = {
name: "Core Object",
version: "1.0"
};
// پلاگین ۱
const plugin1Key = Symbol("plugin1");
coreObject[plugin1Key] = {
description: "پلاگین ۱ قابلیتهای اضافی اضافه میکند",
activate: function() {
console.log("پلاگین ۱ فعال شد");
}
};
// پلاگین ۲
const plugin2Key = Symbol("plugin2");
coreObject[plugin2Key] = {
author: "توسعهدهنده دیگر",
init: function() {
console.log("پلاگین ۲ مقداردهی اولیه شد");
}
};
// دسترسی به پلاگینها
console.log(coreObject[plugin1Key].description); // خروجی: پلاگین ۱ قابلیتهای اضافی اضافه میکند
coreObject[plugin2Key].init(); // خروجی: پلاگین ۲ مقداردهی اولیه شد
در این مثال، هر پلاگین از یک کلید نماد منحصر به فرد استفاده میکند که از تداخل نامهای احتمالی جلوگیری کرده و تضمین میکند که پلاگینها میتوانند به صورت مسالمتآمیز در کنار هم وجود داشته باشند.
۲. افزودن فراداده به عناصر DOM
از نمادها میتوان برای پیوست کردن فراداده به عناصر DOM بدون تداخل با ویژگیها یا خصوصیات موجود آنها استفاده کرد.
const element = document.createElement("div");
const dataKey = Symbol("elementData");
element[dataKey] = {
type: "widget",
config: {},
timestamp: Date.now()
};
// دسترسی به فراداده
console.log(element[dataKey].type); // خروجی: widget
این رویکرد فراداده را از ویژگیهای استاندارد عنصر جدا نگه میدارد، قابلیت نگهداری را بهبود میبخشد و از تداخلات احتمالی با CSS یا سایر کدهای جاوا اسکریپت جلوگیری میکند.
۳. پیادهسازی ویژگیهای خصوصی
اگرچه جاوا اسکریپت ویژگیهای خصوصی واقعی ندارد، میتوان از نمادها برای شبیهسازی حریم خصوصی استفاده کرد. با استفاده از یک نماد به عنوان کلید ویژگی، میتوانید دسترسی کد خارجی به آن ویژگی را دشوار (اما نه غیرممکن) کنید.
class MyClass {
#privateSymbol = Symbol("privateData"); // توجه: این سینتکس '#' یک فیلد خصوصی *واقعی* است که در ES2020 معرفی شده و با این مثال متفاوت است
constructor(data) {
this[this.#privateSymbol] = data;
}
getData() {
return this[this.#privateSymbol];
}
}
const myInstance = new MyClass("Sensitive Information");
console.log(myInstance.getData()); // خروجی: Sensitive Information
// دسترسی به ویژگی "خصوصی" (دشوار، اما ممکن)
const symbolKeys = Object.getOwnPropertySymbols(myInstance);
console.log(myInstance[symbolKeys[0]]); // خروجی: Sensitive Information
اگرچه Object.getOwnPropertySymbols()
هنوز میتواند نماد را آشکار کند، اما احتمال دسترسی یا تغییر تصادفی ویژگی "خصوصی" توسط کد خارجی را کاهش میدهد. توجه: فیلدهای خصوصی واقعی (با استفاده از پیشوند #) اکنون در جاوا اسکریپت مدرن در دسترس هستند و تضمینهای حریم خصوصی قویتری ارائه میدهند.
بهترین شیوهها برای استفاده از نمادها
در اینجا چند بهترین شیوه برای به خاطر سپردن هنگام کار با نمادها آورده شده است:
- از توضیحات توصیفی برای نمادها استفاده کنید: ارائه توضیحات معنادار، اشکالزدایی و ثبت وقایع را آسانتر میکند.
- رجیستری سراسری نمادها را در نظر بگیرید: هنگامی که نیاز به اشتراکگذاری نمادها در ماژولها یا برنامههای مختلف دارید، از
Symbol.for()
استفاده کنید. - از شمارش آگاه باشید: به یاد داشته باشید که ویژگیهای نماد به طور پیشفرض قابل شمارش نیستند و برای دسترسی به آنها از
Object.getOwnPropertySymbols()
استفاده کنید. - از نمادها برای فراداده استفاده کنید: از نمادها برای پیوست کردن فراداده به اشیاء بدون تداخل با ویژگیهای موجود آنها بهره ببرید.
- هنگامی که به حریم خصوصی قوی نیاز است، فیلدهای خصوصی واقعی را در نظر بگیرید: اگر به حریم خصوصی واقعی نیاز دارید، از پیشوند # برای فیلدهای خصوصی کلاس (موجود در جاوا اسکریپت مدرن) استفاده کنید.
نتیجهگیری
نمادهای جاوا اسکریپت مکانیزم قدرتمندی برای ایجاد کلیدهای ویژگی منحصر به فرد، پیوست کردن فراداده به اشیاء و سفارشیسازی رفتار اشیاء ارائه میدهند. با درک نحوه کار نمادها و پیروی از بهترین شیوهها، میتوانید کدهای جاوا اسکریپت قویتر، قابل نگهداریتر و بدون تداخل بنویسید. چه در حال ساخت سیستمهای پلاگین، افزودن فراداده به عناصر DOM یا شبیهسازی ویژگیهای خصوصی باشید، نمادها ابزار ارزشمندی برای بهبود گردش کار توسعه جاوا اسکریپت شما فراهم میکنند.